package com.shu.logsearch.es.service.impl;

import com.shu.logsearch.config.ElasticSearchProperties;
import com.shu.logsearch.es.model.Doc;
import com.shu.logsearch.es.service.ESRestService;
import com.shu.logsearch.util.MyJsonUtil;
import com.shu.logsearch.util.TypeCastUtil;
import org.apache.http.HttpHost;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @author Tree Cai
 * @date 2019/6/27
 */
@Service
public class ESRestServiceImpl implements ESRestService {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private ElasticSearchProperties esProperties;


    /**
     * 获取ES客户端实例
     *
     * @return
     */
    private RestHighLevelClient getRestHighLevelClient() {
        RestHighLevelClient client = new RestHighLevelClient(
                RestClient.builder(new HttpHost(esProperties.getHost(), esProperties.getPort(), "http")));
        return client;
    }

    @Override
    public Boolean addIndex(String index, int shardNum, int replicNum, String jsonSource) {
        logger.info(String.format("开始创建索引，indexName：%s，shardNum：%s，replicNum：%s，jsonSource：%s", index, shardNum, replicNum, jsonSource));
        RestHighLevelClient esClient = getRestHighLevelClient();
        CreateIndexRequest createIndexRequest = new CreateIndexRequest(index);
        //设置分片和副本
        createIndexRequest.settings(Settings.builder()
                .put("index.number_of_shards", shardNum)
                .put("index.number_of_replicas", replicNum)
        );
        createIndexRequest.mapping(jsonSource, XContentType.JSON);
        CreateIndexResponse createIndexResponse = null;
        try {
            createIndexResponse = esClient.indices().create(createIndexRequest, RequestOptions.DEFAULT);
        } catch (Exception e) {
            logger.info(String.format("创建索引【%s】，indexName：%s", "失败", index));
            e.printStackTrace();
        }
        Boolean success = createIndexResponse.isAcknowledged();
        logger.info(String.format("创建索引【%s】，indexName：%s", success==true ? "成功" : "失败", index));
        return success;
    }

    @Override
    public Boolean deleteIndex(String index) {
        RestHighLevelClient esClient = getRestHighLevelClient();
        DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(index);
        try {
            AcknowledgedResponse deleteIndexResponse = esClient.indices().delete(deleteIndexRequest, RequestOptions.DEFAULT);
            return deleteIndexResponse.isAcknowledged();
        } catch (ElasticsearchException | IOException e) {
            logger.info("删除索引失败！indexName:" + index);
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public Boolean indexDocs(String index, List<Doc> docList) {
        logger.info("开始批量索引！indexName：" + index);
        RestHighLevelClient esClient = getRestHighLevelClient();
        BulkRequest bulkRequest = new BulkRequest();
        for (Doc doc : docList)
            bulkRequest.add(new IndexRequest(index).source(MyJsonUtil.parseObject(doc)));
        try {
            BulkResponse bulkResponse = esClient.bulk(bulkRequest, RequestOptions.DEFAULT);
            if (bulkResponse.hasFailures()) {
                logger.error("批量索引失败！");
                return false;
            }
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        logger.info("批量索引成功！indexName：" + index);
        return true;
    }

    @Override
    public Boolean indexDocs(String index, String docJsonStr) {
        RestHighLevelClient esClient = getRestHighLevelClient();
        IndexRequest indexRequest = new IndexRequest(index).source(docJsonStr, XContentType.JSON);
        try {
            IndexResponse indexResponse = esClient.index(indexRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    @Override
    public List<Map<String, Object>> searchDocs(String[] indices, String keyWord, String[] fieldNames, int pageNum, int pageSize) {
        List<Map<String, Object>> docList = new ArrayList<>();
        RestHighLevelClient esClient = getRestHighLevelClient();
        SearchRequest searchRequest = new SearchRequest(indices);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(QueryBuilders.multiMatchQuery(keyWord, fieldNames).operator(Operator.AND));
        searchSourceBuilder.highlighter(getHighlightBuilder(fieldNames));
        searchSourceBuilder.from((pageNum - 1) * pageSize);
        searchSourceBuilder.size(pageSize);
        searchRequest.source(searchSourceBuilder);
        try {
            SearchResponse searchResponse = esClient.search(searchRequest, RequestOptions.DEFAULT);
            setHighlightHit(searchResponse.getHits());
            for (SearchHit searchHit : searchResponse.getHits()) {
                docList.add(searchHit.getSourceAsMap());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return docList;
    }

    @Override
    public Boolean existIndex(String index) {
        GetIndexRequest request = new GetIndexRequest(index);
        try {
            return getRestHighLevelClient().indices().exists(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }


    @Deprecated
    private Doc stringObjectMapToDoc(Map<String, Object> stringObjectMap) {
        Doc doc = new Doc();
        if (stringObjectMap == null)
            return doc;
        doc.setTitle(TypeCastUtil.toString(stringObjectMap.get("title")));
        doc.setSubTitle(TypeCastUtil.toString(stringObjectMap.get("subTitle")));
        doc.setContent(TypeCastUtil.toString(stringObjectMap.get("content")));
        doc.setCreateTime(TypeCastUtil.toDate(stringObjectMap.get("createTime")));
        doc.setUpdateTime(TypeCastUtil.toDate(stringObjectMap.get("updateTime")));
        return doc;
    }

    private HighlightBuilder getHighlightBuilder(String[] fields) {
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        for (String field : fields) {
            HighlightBuilder.Field highlightFileld = new HighlightBuilder.Field(field);
            highlightBuilder.field(highlightFileld);
        }
        highlightBuilder.preTags("<span style=color:red>").postTags("</span>");
        return highlightBuilder;
    }

    /**
     * 将搜索结果处理为高亮显示
     *
     * @param hits
     */
    private void setHighlightHit(SearchHits hits) {
        for (SearchHit hit : hits) {
            Map<String, Object> stringObjectMap = hit.getSourceAsMap();
            hit.getHighlightFields().forEach((str, highlightField) -> {
                StringBuilder text = new StringBuilder();
                for (Text fragment : highlightField.getFragments()) {
                    text.append(fragment);
                }
                stringObjectMap.put(str, text.toString());
            });
        }
    }
}
